﻿using System.Diagnostics.CodeAnalysis;

namespace System;

public static class TypeExtensions
{
    /// <summary>
    /// 获取类型全称（包括程序集名称）
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static string GetFullNameWithAssemblyName([NotNull] this Type type)
    {
        ArgumentNullException.ThrowIfNull(type);

        return type.FullName + ", " + type.Assembly.GetName().Name;
    }

    /// <summary>
    /// 确定此类型的一个实例是否可以赋值给 <typeparamref name="TTarget"></typeparamref> 类型的一个实例
    ///
    /// 在内部使用 <see cref="Type.IsAssignableTo"/> 方法
    /// </summary>
    /// <typeparam name="TTarget">目标类型</typeparam>
    public static bool IsAssignableTo<TTarget>([NotNull] this Type type)
    {
        ArgumentNullException.ThrowIfNull(type);

        return type.IsAssignableTo(typeof(TTarget));
    }

    /// <summary>
    /// 确定此类型的一个实例是否可以赋值给 <paramref name="targetType"></paramref> 类型的一个实例
    ///
    /// 在内部使用 <see cref="Type.IsAssignableFrom"/> 方法
    /// </summary>
    /// <param name="type">要赋值的类型</param>
    /// <param name="targetType">目标类型</param>
    public static bool IsAssignableTo([NotNull] this Type type, [NotNull] Type targetType)
    {
        ArgumentNullException.ThrowIfNull(type);
        ArgumentNullException.ThrowIfNull(targetType);

        return targetType.IsAssignableFrom(type);
    }

    /// <summary>
    /// 获取此类型的所有基类
    /// </summary>
    /// <param name="type">要获取其基类的那个类型</param>
    /// <param name="includeObject">为 true 时，会将标准的 <see cref="object"/> 类型包含在返回的数组中</param>
    public static Type[] GetBaseClasses([NotNull] this Type type, bool includeObject = true)
    {
        ArgumentNullException.ThrowIfNull(type);

        var types = new List<Type>();
        AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject);
        return types.ToArray();
    }

    /// <summary>
    /// 获取此类型的所有基类
    /// </summary>
    /// <param name="type">要获取其基类的那个类型</param>
    /// <param name="stoppingType">一个用于停止继续往更深入的基类查找的类型。该类型将被包含在返回的数组中</param>
    /// <param name="includeObject">为 true 时，会将标准的 <see cref="object"/> 类型包含在返回的数组中</param>
    public static Type[] GetBaseClasses([NotNull] this Type type, Type stoppingType, bool includeObject = true)
    {
        ArgumentNullException.ThrowIfNull(type);

        var types = new List<Type>();
        AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType);
        return types.ToArray();
    }

    private static void AddTypeAndBaseTypesRecursively(
        [NotNull] List<Type> types,
        Type? type,
        bool includeObject,
        Type? stoppingType = null)
    {
        if (type == null || type == stoppingType)
        {
            return;
        }

        if (!includeObject && type == typeof(object))
        {
            return;
        }

        AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType);
        types.Add(type);
    }
}
